第18天,我開始在 Vue 3、Angular 19 和 Svelte 5 中進行 Github 使用者個人資料的練習。
我的 CSS 技術不強,因此我會遵循講師的建議使用 DaisyUI 來設計卡片樣式。我會把樣式放最後,集中精力在資料擷取和父子元件間的溝通。
這個小專案將分成三個部分:
讓我們先從資料擷取開始,你會發現三個框架在呼叫 API 取得資料的方法各不相同。
Vue 3 使用 Composable。
Angular 20 使用實驗性的 httpResource,因為我想嘗試不同。
Svelte 5 在 page.ts 中使用 data loader。
在透過使用者名稱取得 Github 個人資料之前,必須先建立 Github personal access token
在 Vue 3 和 Svelte 5 中,由於使用 Vite,建立環境變數相對簡單,與 Angular 不同。
VITE_GITHUB_TOKEN=<github personal access token>
建立一個環境變數來儲存 Github personal access token,該變數必須以 VITE 為前綴 (prefix)。
VITE_GITHUB_TOKEN=github_xxx
export type GithubProfile = {
login: string;
name: string;
followers: number;
following: number;
html_url: string;
avatar_url: string;
bio: string | null;
public_repos: number;
}
GithubProfile
類型在所有三個應用程式中相同。
一個 Github 個人資料包含使用者名稱、姓名、追蹤者數量、追蹤中數量、HTML URL、頭像 URL、可為 null 的個人簡介(bio)以及公開的 repositories 數量。
composables
資料夾composables
資料夾中建立一個 useGithubProfile.ts
的 composable,用來根據使用者名稱取得 Github 個人資料。此新的 composable 定義了三個 refs,分別用來儲存使用者名稱、個人資料和錯誤訊息。
export function useGithubProfile() {
... composable logic ...
}
export function useGithubProfile() {
const username = ref('')
const profile = ref<GithubProfile | null>(null)
const error = ref('');
}
該 composable 會在 watch 中監控 username
ref,並發出 fetch 呼叫以取得個人資料。
export function useGithubProfile() {
const username = ref('')
const profile = ref<GithubProfile | null>(null)
const error = ref('');
watch(username, async (newUserName) => {
if (!newUserName) {
profile.value = null;
error.value = '';
return;
}
profile.value = null;
error.value = '';
fetch(`https://api.github.com/users/${newUserName}`, {
headers: {
Authorization: `Bearer ${import.meta.env.VITE_GITHUB_TOKEN}`,
}
})
.then( async response => {
if (!response.ok) {
error.value = 'Network response was not ok.'
}
profile.value = await response.json() as GithubProfile;
})
.catch(err => {
console.error('There has been a problem with your fetch operation:', err);
if (err instanceof Error) {
error.value = err.message
}
error.value = 'Failed to fetch profile.'
});
});
}
watch
回呼函式 (callback) 會重設 profile
和 error
的 refs。Fetch 呼叫 Github API 以取得使用者個人資料。Authorization
標頭 (header) 會將個人 Github Token 作為 bearer token
儲存。
當 HTTP 請求成功時,profile
ref 會更新為 JSON 回應;當發生 HTTP 錯誤時,error
ref 會儲存錯誤訊息。
export function useGithubProfile() {
... previous logic ...
return {
username,
profile,
error,
}
}
最後,composable 會回傳 ref,以便在元件中可以存取它。
已建立 routes/+page.ts
檔案來從GitHub 使用者名稱清單中載入資料。該 TypeScript 檔案包含一個 load
函式,此函式會迭代使用者名稱,呼叫 fetch 取得個人資料,並將這些資料以清單形式回傳。
export const load: PageLoad = async () => {
const usernames = ['johnsoncodehk', 'antfu', 'railsstudent', 'danielkellyio', 'hootlex', 'MooseSaeed'];
const profilesSettled = await Promise.allSettled(
usernames.map((username) => fetchGithubProfile(username))
);
const profiles = profilesSettled.reduce((acc, result, idx) => {
if (result.status === 'fulfilled') {
return acc.concat({
key: idx,
profile: result.value,
});
} else {
return acc.concat({
key: idx,
error: `Error fetching profile: ${result.reason}`
});
}
}, [] as GithubProfileItem[])
return {
data: profiles
};
};
Promise.allSettled
確保 fetch 呼叫無論成功或失敗都會完成。當請求成功時,清單會儲存唯一 key 和個人資料。否則,清單元素會包含唯一的 key 和錯誤訊息。
唯一 key 是在 Github Profile List 元件的列表中渲染個人資料所需的。
fetchGithubProfile
函式與 Vue Composable 中的 fetch 呼叫類似。
export function fetchGithubProfile(username: string, ): Promise<GithubProfile> {
const url = `https://api.github.com/users/${username}`;
return fetch(url, {
headers: {
Authorization: `Bearer ${import.meta.env.VITE_GITHUB_TOKEN}`,
}
})
.then(async (response) => {
if (!response.ok) {
throw new Error(`GitHub profile not found for user: ${username}`);
}
const result = await response.json() as Promise<GithubProfile>;
return result;
})
.catch((error) => {
console.error('Error fetching GitHub profile:', error);
throw error;
});
}
當 HTTP 回應成功時,該函式會等待 JSON 回應並回傳 JSON 資料。當 HTTP 回應失敗時,該函式會記錄錯誤並拋出錯誤訊息。
在 angular.json
中,定義 GITHUB_TOKEN
變數。
"define": {
"GITHUB_TOKEN": "'secret value'"
}
secret_value
是一個虛擬值,會 command line 中被覆寫。
新增 src/types.d.ts
並宣告 GITHUB_TOKEN
常數。
declare const GITHUB_TOKEN: string;
export GITHUB_TOKEN=<github personal token>
ng build --define GITHUB_TOKEN=\'$GITHUB_TOKEN\'
serve dist
可於瀏覽器中開啟 http://localhost:3000
以啟動 Angular 應用程式。
另外,在 angular.json
中,architect -> build -> options 下,請新增 output_path
設定。將編譯輸出目錄設為 dist
資料夾,而非 dist/angular-github-profile
子資料夾。
"outputPath": {
"base": "dist",
"browser": ""
}
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withFetch()),
]
};
為了在 Angular 應用程式中使用 httpResource
,app 設定的 providers
陣列必須包含 provideHttpClient
提供者 (provider)。HttpClient
預設使用 XmlHttpRequest
,我透過 withFetch()
功能將其覆寫成使用 Fetch API。此做法是為了模擬 Vue 3 和 Svelte 5 範例的行為。
在 Angular 元件中定義 httpResource
是最簡單的方法。因此,我建立了一個新的 GithubProfileCardComponent
。
@Component({
selector: 'app-github-profile-card',
template: `Working`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GithubProfileCardComponent {}
該元件目前顯示靜態文字,因為尚無資料。
username = input.required<string>();
profileResource = httpResource<GithubProfile>(() => this.username() ? {
url: `https://api.github.com/users/${this.username()}`,
method: 'GET',
headers: {
Authorization: `Bearer ${GITHUB_TOKEN}`
}
}: undefined, {
equal: (a, b) => a?.login === b?.login,
}
);
username
是一個 input signal
,用來保存 Github 使用者名稱。
username
是響應式的,當使用者名稱變更時,profileResource
會發出 HTTP 請求以取得個人資料資源。
{
equal: (a, b) => a?.login === b?.login,
}
當兩個個人資料具有相同的登入帳號時,profileResource
不會觸發變更偵測,且模板不會重新渲染。
profile = computed(() => this.profileResource.hasValue() ?this.profileResource.value() : undefined);
error = computed(() => this.profileResource.error()?.message || '');
profile
是一個計算信號 (computed signal),用來取得 Github 個人資料的值。它會顯示資源值或未定義。
error
計算信號 (computed signal) 會顯示錯誤訊息或空字串。
我們已成功在三個框架中取得 Github 個人資料。